<template>
  <div class="relative" @mouseenter="onMouseenter" @mouseleave="onMouseleave">
    <div ref="referenceTarget">
      <!-- 具名插槽：出发弹层的视图 -->
      <slot name="reference" />
    </div>
    <transition name="slide">
      <div
        v-if="isVisable"
        ref="contentTarget"
        class="absolute p-1 z-20 bg-white dark:bg-zinc-900 border dark:border-zinc-700 rounded-md"
        :style="contentStyle"
      >
        <!-- 匿名插槽：弹出层视图中展示的内容 -->
        <slot />
      </div>
    </transition>
  </div>
</template>

<script setup>
import { ref, watch, nextTick } from 'vue'
import {
  PROP_ARR,
  PROP_BOTTOM_LEFT,
  PROP_TOP_LEFT,
  PROP_TOP_RIGHT,
  PROP_BOTTOM_RIGHT,
  DEPLAY_TIME
} from '@/constants/theme'

const props = defineProps({
  // 气泡位置
  placement: {
    type: String,
    default: PROP_BOTTOM_LEFT,
    validator: (value) => {
      const result = PROP_ARR.includes(value)
      if (!result) {
        throw new Error(`placement 的值只能是 ${PROP_ARR.join(' | ')}`)
      }
      return result
    }
  }
})

const isVisable = ref(false)

let timeout = null
/**
 * 鼠标移入
 */
const onMouseenter = () => {
  isVisable.value = true
  // 清除定时器
  if (timeout) {
    clearTimeout(timeout)
    // timeout = null
  }
}

/**
 * 鼠标移出
 */
const onMouseleave = () => {
  timeout = setTimeout(() => {
    isVisable.value = false
    timeout = null
  }, DEPLAY_TIME)
}

/**
 * 计算元素尺寸
 */
const referenceTarget = ref(null)
const contentTarget = ref(null)
const getElementSize = (target) => {
  if (!target) return {}
  const { width, height } = target.getBoundingClientRect()
  return { width, height }
}

/**
 * 气泡样式
 */
const contentStyle = ref({
  top: 0,
  left: 0
})

// 计算
watch(isVisable, (value) => {
  if (!value) return
  // 等待元素渲染完成
  nextTick(() => {
    const referenceSize = getElementSize(referenceTarget.value)
    const contentSize = getElementSize(contentTarget.value)
    switch (props.placement) {
      case PROP_TOP_LEFT:
        contentStyle.value = {
          top: 0,
          left: `-${contentSize.width}px`
        }
        break
      case PROP_TOP_RIGHT:
        contentStyle.value = {
          top: 0,
          left: `${referenceSize.width}px`
        }
        break
      case PROP_BOTTOM_LEFT:
        contentStyle.value = {
          top: `${referenceSize.height}px`,
          left: `-${contentSize.width}px`
        }
        break
      case PROP_BOTTOM_RIGHT:
        contentStyle.value = {
          top: `${referenceSize.height}px`,
          left: `${referenceSize.width}px`
        }
        break
    }
  })
})
</script>

<style lang="scss" scoped>
.slide-enter-active,
.slide-leave-active {
  transition: opacity 0.3s, transform 0.3s;
}
.slide-enter-from,
.slide-leave-to {
  opacity: 0;
  transform: translateY(20px);
}
</style>
